package com.jintian.smart.kernel.autoconfigure.beetl;

import com.alibaba.druid.filter.Filter;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.wall.WallConfig;
import com.alibaba.druid.wall.WallFilter;
import com.jintian.smart.kernel.autoconfigure.ConfigConstants;
import com.jintian.smart.kernel.autoconfigure.nacos.NacosTools;
import com.jintian.smart.kernel.common.util.HomeUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.Ordered;
import org.springframework.core.ResolvableType;
import org.springframework.core.env.*;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

import javax.sql.DataSource;
import java.io.File;
import java.sql.SQLException;
import java.util.Properties;
import java.util.function.Supplier;

/**
 * 加载nacos数据源配置，并注册数据源。Ordered.HIGHEST_PRECEDENCE + 10 after
 * com.alibaba.nacos.spring.core.env.NacosPropertySourcePostProcessor
 */
@Slf4j
public class DataSourceNacosBeanRegistryProcessor implements BeanDefinitionRegistryPostProcessor, ResourceLoaderAware, ApplicationContextAware, Ordered {
    ApplicationContext applicationContext;
    ResourceLoader resourceLoader;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }

    private DataSourceProperties getNacosDataSourceProperties(Properties nacosProperties) {
        DataSourceProperties dsProperties = new DataSourceProperties();
        ConfigurableEnvironment environment = (ConfigurableEnvironment) applicationContext.getEnvironment();
        PropertySource<?> databasePropertySource = NacosTools.buildPropertySource(ConfigConstants.NACOS_DEFAULT_DATAID_DATABASEPROVIDER, nacosProperties, environment);
        try {
            Binder binder = Binder.get(environment);

            ResolvableType type = ResolvableType.forClass(dsProperties.getClass());
            Bindable<?> target = Bindable.of(type).withExistingValue(dsProperties);
            binder.bind(ConfigConstants.PROPERTIES_PREFIX_DATASOURCE, target);
        } finally {
            if (databasePropertySource != null)
                environment.getPropertySources().remove(databasePropertySource.getName());
        }
        return dsProperties;
    }

    private DataSourceProperties getLoalDataSourceProperties() {
        DataSourceProperties dsProperties = new DataSourceProperties();
        ConfigurableEnvironment environment = (ConfigurableEnvironment) applicationContext.getEnvironment();
        Binder binder = Binder.get(environment);

        ResolvableType type = ResolvableType.forClass(dsProperties.getClass());
        Bindable<?> target = Bindable.of(type).withExistingValue(dsProperties);
        binder.bind(ConfigConstants.PROPERTIES_PREFIX_DATASOURCE, target);
        return dsProperties;
    }

    private void registerBeanDefinitions(BeanDefinitionRegistry registry) throws BeansException {
        ApplicationContext parent = applicationContext.getParent();
        if (parent != null) {
            return;
        }
        ConfigurableEnvironment appEnv = (ConfigurableEnvironment) applicationContext.getEnvironment();
        boolean local = "true".equals(appEnv.getProperty("smart.config.local"));
        DataSourceProperties dbProvider;
        Properties nacosProperties = null;
        if (local) {
            dbProvider = getLoalDataSourceProperties();
        } else {
            // 1. nacos属性
            nacosProperties = NacosTools.buildNacosProperties((ConfigurableEnvironment) applicationContext.getEnvironment());
            // 2.nacos或Environment中加载DatabaseProvider
            dbProvider = getNacosDataSourceProperties(nacosProperties);
        }
        // 3.nacos中加载数据源
        String[] dsIds = dbProvider.getDsIds().split(",");
        for (int i = 0; i < dsIds.length; i++) {
            String dsID = dsIds[i];
            if (local) {
//                String appHome = System.getProperty("APP_HOME", System.getenv("APP_HOME"));
                String appHome = HomeUtil.getAppHome();
                String fileName = dsID;

                String yamlPath = appHome + File.separator + "conf" + File.separator + "ds." + dsID + ".yml";
                //Springboot读取yml配置
                YamlPropertiesFactoryBean yaml = new YamlPropertiesFactoryBean();
                yaml.setResources(new FileSystemResource(yamlPath));
                MutablePropertySources propertySources = appEnv.getPropertySources();
                propertySources.addFirst(new PropertiesPropertySource(fileName, yaml.getObject()));

                registerDatasource(registry, fileName, new String[]{""}, appEnv, i == 0);
            } else {
                // 创建数据源的Binder独立环境，添加app的dbProvider的通用配置。确保没有其他配置文件进行污染
                ConfigurableEnvironment environment = new StandardEnvironment() {
                    @Override
                    protected void customizePropertySources(MutablePropertySources propertySources) {
                        propertySources.addLast(new MapPropertySource(StandardEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, dbProvider.getCommon()));
                    }
                };
                registerDatasource(registry, dsID, new String[]{""}, environment, nacosProperties, i == 0);
            }
        }
    }

    void registerDatasource(BeanDefinitionRegistry registry, String dsId, String[] prefixes, ConfigurableEnvironment environment, Properties globalProperties, boolean primary) {
        PropertySource<?> dsPropertySource = NacosTools.buildPropertySource(ConfigConstants.getDatasourceDataId(dsId), globalProperties, environment);
        if (dsPropertySource == null)
            return;
        registerDatasource(registry, dsId, prefixes, environment, primary);
    }

    void registerDatasource(BeanDefinitionRegistry registry, String dsId, String[] prefixes, ConfigurableEnvironment environment, boolean primary) {
        String dsName = ConfigConstants.getDatasourceBeanName(dsId);
        // 注册数据源
        {
            Binder binder = Binder.get(environment);
            final DruidDataSource datasource = new DruidDataSource();

            ResolvableType type = ResolvableType.forClass(DruidDataSource.class);
            Bindable<?> target = Bindable.of(type).withExistingValue(datasource);
            for (String prefix : prefixes) {
                binder.bind(prefix, target);
            }

            BeanDefinitionBuilder bdb = BeanDefinitionBuilder.rootBeanDefinition(DataSource.class,
                    new Supplier<DataSource>() {
                        @Override
                        public DataSource get() {
                            return datasource;
                        }
                    });
            log.info("build datasource id={},beanName={},url={}", dsId, dsName, datasource.getUrl());
            try {
                datasource.init();
                fixedDruidDataSource(datasource);
            } catch (SQLException e) {
            }
            // bdb.setInitMethodName("init");
            if (primary) {
                bdb.setPrimary(true);
            }
            registry.registerBeanDefinition(dsName, bdb.getBeanDefinition());
        }

        // 注册事务
        {
            BeanDefinitionBuilder bdb = BeanDefinitionBuilder.rootBeanDefinition(DataSourceTransactionManager.class);
            bdb.addConstructorArgReference(dsName);
            if (primary) {
                bdb.setPrimary(true);
            }
            String name = ConfigConstants.getTransactionManagerBeanName(dsId);
            registry.registerBeanDefinition(name, bdb.getBeanDefinition());
            // TODO 原系统已经注册transactionManager，暂不注册
            if (primary) {
                registry.registerAlias(name, "transactionManager");
            }
        }
        log.info("register bean {} with datasource {}", dsName, dsId);
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    }

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        this.registerBeanDefinitions(registry);
    }

    @Override
    public int getOrder() {
        // after com.alibaba.nacos.spring.core.env.NacosPropertySourcePostProcessor
        return Ordered.HIGHEST_PRECEDENCE + 10;
    }

    /**
     * 修正Druid防火墙
     *
     * @param druidDataSource
     */
    private static void fixedDruidDataSource(DruidDataSource druidDataSource) {
        for (Filter filter : druidDataSource.getProxyFilters()) {
            if (filter instanceof WallFilter) {
                WallConfig config = ((WallFilter) filter).getConfig();
                config.setCommentAllow(true);
                config.setConditionDoubleConstAllow(true);
                config.setConditionAndAlwayTrueAllow(true);
                config.setMultiStatementAllow(true);
                break;
            }
        }
    }
}
